<?php

/**
 * A simple single sign-on server using PHP session linking.
 * Don't use session.auto_start or session_start().
 */
class SingleSignOn_Server
{
	/**
	 * Path to link files. Set this to use link files instead of symlinks.
	 * Don't forget to clean up old session files once in a while.
	 * 
	 * @var string
	 */
	public $links_path;

    /**
     * Flag to indicate the sessionStart has been called
     * @var boolean
     */
    protected $started=false;

    /**
     * Information of the brokers.
     * This should be data in a database.
     * 
     * @var array
     */
    protected static $brokers = array(
        'ALEX' => array('secret'=>"abc123"),
        'BINCK' => array('secret'=>"xyz789"),
		'UZZA' => array('secret'=>"rino222"),
        'AJAX' => array('secret'=>"amsterdam"),
        'LYNX' => array('secret'=>"klm345"),
    );

    /**
     * Information of the users.
     * This should be data in a database.
     * 
     * @var array
     */
    protected static $users = array(
        'jan' => array('password'=>"jan1", 'fullname'=>"Jan Smit", 'email'=>"jan@smit.nl"),
        'peter' => array('password'=>"peter1", 'fullname'=>"Peter de Vries", 'email'=>"peter.r.de-vries@sbs.nl"),
        'bart' => array('password'=>"bart1", 'fullname'=>"Bart de Graaf", 'email'=>"graaf@bnn.info"),
        'henk' => array('password'=>"henk1", 'fullname'=>"Henk Westbroek", 'email'=>"henk@amsterdam.com")
    );
    
    /**
     * The current broker
     * @var string
     */
    protected $broker = null;
    
    private $_Connection = "";
	private $_Db = false;
	private $_DbHost = "localhost";
	private $_DbUser = "zhiyong";
	private $_DbPass = "hihihi";
	private $_DbName = "dkmtest";
    
	private $expireTime = 0;
	private $expireDays = 365;
	private $serverToken = '';
    /**
     * Class constructor.
     */
    public function __construct()
    {
    	$this->_Connection = mysql_connect($this->_DbHost,$this->_DbUser,$this->_DbPass);
		$this->_Db = mysql_select_db($this->_DbName, $this->_Connection) or die(mysql_error());
		
		// Delete all expire information
		$thisTime = time(); 
		$this->query("DELETE FROM even_sso_server_token WHERE serverExpire < ".$thisTime);
		$this->query("DELETE FROM even_sso_broker_token WHERE brokerExpire < ".$thisTime);
		
		if(isset($_COOKIE['server_token'])) $this->serverToken = $_COOKIE['server_token'];
		
    	//if (!function_exists('symlink')) $this->links_path = sys_get_temp_dir();
    }
    
    public function closeDB() {
    	mysql_close($this->_Connection);
    }
    
    /**
     * Start session and protect against session hijacking
     */
    protected function sessionStart()
    {
        //if ($this->started) return;
        //$this->started = true;

        // Broker session
/*        $matches = null;
        if (isset($_REQUEST[session_name()]) && preg_match('/^SSO-(\w*+)-(\w*+)-([a-z0-9]*+)$/', $_REQUEST[session_name()], $matches)) {
	        $sid = $_REQUEST[session_name()];
	        
	    	if (isset($this->links_path) && file_exists("{$this->links_path}/$sid")) {
	    		session_id(file_get_contents("{$this->links_path}/$sid"));
	    		session_start();
	    		setcookie(session_name(), "", 1);
	    	} else {
				session_start();
	    	}

            if (!isset($_SESSION['client_addr'])) {
                session_destroy();
                $this->fail("Not attached");
            }
            
            if ($this->generateSessionId($matches[1], $matches[2], $_SESSION['client_addr']) != $sid) {
                session_destroy();
                $this->fail("Invalid session id");
            }

            $this->broker = $matches[1];
            return;
        }*/

        // User session
        //session_start();
        if(isset($this->serverToken) && $this->serverToken != '') return;
        //if(isset($_SESSION['is_started']) || $_SESSION['is_started']) return;
        
		$sso_request = array_merge($_GET, $_POST);
		
        $this->serverToken = md5(uniqid(rand(), true));
        if(isset($sso_request['expire']) && $sso_request['expire'] > 0) $this->expireTime = $sso_request['expire'];
        else $this->expireTime = time() + $this->expireDays*86400;
        setcookie('server_token', $this->serverToken, $this->expireTime);
        
    	$this->query("INSERT INTO even_sso_server_token (serverToken,serverExpire) VALUES ('".$this->serverToken."',".$this->expireTime.")",$this->_Connection);
		//$_SESSION['is_started'] = 1;
		
		
        //if (isset($_SESSION['client_addr']) && $_SESSION['client_addr'] != $_SERVER['REMOTE_ADDR']) session_regenerate_id(true);
        //if (!isset($_SESSION['client_addr'])) $_SESSION['client_addr'] = $_SERVER['REMOTE_ADDR'];
    }
    
    /**
     * Generate session id from session token
     * 
     * @return string
     */
    protected function generateSessionId($broker, $token, $client_addr=null)
    {
        if (!isset(self::$brokers[$broker])) return null;

        if (!isset($client_addr)) $client_addr = $_SERVER['REMOTE_ADDR'];
        return "SSO-{$broker}-{$token}-" . md5('session' . $token . $client_addr . self::$brokers[$broker]['secret']);
    }
    
    /**
     * Generate session id from session token
     * 
     * @return string
     */
    protected function generateAttachChecksum($broker, $token)
    {
        if (!isset(self::$brokers[$broker])) return null;
        return md5('attach' . $token . $_SERVER['REMOTE_ADDR'] . self::$brokers[$broker]['secret']);
    }
    
    /**
     * Authenticate
     */
    public function login()
    {
        //$this->sessionStart();

		$sso_request = array_merge($_GET, $_POST);
		
        if (empty($sso_request['member_id']) || empty($sso_request['pass_hash'])) $this->failLogin("No member specified");
        if (empty($sso_request['session_id'])) $this->failLogin("No session specified");
        if (empty($sso_request['broker_token'])) $this->failLogin("No broker token specified");
        //if (!isset(self::$users[$_POST['username']]) || self::$users[$_POST['username']]['password'] != $_POST['password']) $this->failLogin("Incorrect credentials");
        
        $session_id = $sso_request['session_id'];
        $member_id	= $sso_request['member_id'];
        $pass_hash  = $sso_request['pass_hash'];
        $broker_token = $sso_request['broker_token'];
        
        $result = $this->getServerSessionByBrokerToken($broker_token);
    	
        //echo $serverSession['serverSessionId'];
        $this->query("UPDATE even_sso_server_token SET serverSessionId='".$session_id."',
        												serverMemberId=".$member_id.",
        												serverPassHash='".$pass_hash."'
        											WHERE serverTokenId=".$result['serverTokenId'] );
        if(!empty($sso_request['redirect'])) {
        	$serverToken = $_COOKIE['server_token'];
        	setcookie('server_token',$serverToken,0);
        	
            header("Location: " . urldecode($sso_request['redirect']), true, 307);
            exit;        
        }
        
        $this->info();
    }

    /**
     * Log out
     */
    public function logout()
    {
        //$this->sessionStart();
        //unset($_SESSION['session_id']);
        //unset($_SESSION['member_id']);
        //unset($_SESSION['pass_hash']);
		$sso_request = array_merge($_GET, $_POST);
        $brokerToken = $sso_request['broker_token'];
        $result = $this->getServerSessionByBrokerToken($brokerToken);
        
        $this->query("UPDATE even_sso_server_token SET serverSessionId='',
        												serverMemberId=0,
        												serverPassHash=''
        											WHERE serverTokenId=".$result['serverTokenId']);
        echo 1;
    }
    
    private function query($query="") {
    	$q = mysql_query($query) or die (mysql_error()."<br />".$query);
    	
    	return $q;
    }
    
    private function getServerSessionByBrokerToken($brokerToken="")
    {
    	$q = $this->query("SELECT s.* FROM even_sso_server_token s 
    						LEFT JOIN even_sso_broker_token b ON b.serverTokenId=s.serverTokenId
    						WHERE b.brokerToken='".$brokerToken."'");
        $result = mysql_fetch_assoc($q);
        
        return $result;
    }
    
    /**
     * Attach a user session to a broker session 
     */
    public function attach()
    {
        $this->sessionStart();
        //$brokerSession = $_REQUEST['session'];
        
		$sso_request = array_merge($_GET, $_POST);
		
        if (empty($sso_request['broker'])) $this->fail("No broker specified");
        if (empty($sso_request['token'])) $this->fail("No token specified");
        if (empty($sso_request['checksum']) || $this->generateAttachChecksum($sso_request['broker'], $sso_request['token']) != $sso_request['checksum']) $this->fail("Invalid checksum");
/*
        if (!isset($this->links_path)) {
	        $link = (session_save_path() ? session_save_path() : sys_get_temp_dir()) . "/sess_" . $this->generateSessionId($_REQUEST['broker'], $_REQUEST['token']);
	        // Added by BabyWolf
	        $target = (session_save_path() ? session_save_path() : sys_get_temp_dir()) . "/sess_" . session_id();
	        // Modified by BabyWolf
	        if (!file_exists($link)) $attached = symlink($target, $link);
	        if (!$attached) trigger_error("Failed to attach; Symlink wasn't created.", E_USER_ERROR);
        } else {
	        $link = "{$this->links_path}/" . $this->generateSessionId($_REQUEST['broker'], $_REQUEST['token']);
	        if (!file_exists($link)) $attached = file_put_contents($link, session_id());
	        if (!$attached) trigger_error("Failed to attach; Link file wasn't created.", E_USER_ERROR);
		}
*/
        $brokerToken = $sso_request['token'];
        $q = $this->query("SELECT * FROM even_sso_server_token WHERE serverToken='".$this->serverToken."'");
        $result = mysql_fetch_assoc($q);
        
        if($this->expireTime==0) $this->expireTime = $result['serverExpire'];
        
        $this->query("INSERT INTO even_sso_broker_token (brokerToken,serverTokenId,brokerExpire) 
        				VALUES ('".$brokerToken."',".intval($result['serverTokenId']).",".intval($this->expireTime).")",$this->_Connection);
        
        if (isset($sso_request['redirect'])) {
            header("Location: " . $sso_request['redirect'], true, 307);
            exit;        
        }
        
        // Output an image specially for AJAX apps
        header("Content-Type: image/png");
        readfile("empty.png");
    }
    
    /**
     * Ouput user information as XML.
     * Doesn't return e-mail address to brokers with security level < 2.
     */
    public function info()
    {
        //$this->sessionStart();
    	//if (!isset($_SESSION['session_id'])) $this->failLogin("Not logged in");
		$sso_request = array_merge($_GET, $_POST);
    	$brokerToken = $sso_request['broker_token'];
    	$result = $this->getServerSessionByBrokerToken($brokerToken);
    	
/*    	if($result['serverToken'] != $this->serverToken) {
    		$result = array('serverTokenId'	=> 0,
    						'serverMemberId'=> 0,
    						'serverSessionId' => '',
    						'serverPassHash' => '');
    	}*/
    	
        header('Content-type: text/xml; charset=UTF-8');
    	echo '<?xml version="1.0" encoding="UTF-8" ?>', "\n";
    	
    	echo '<user identity="' . htmlspecialchars($result['serverTokenId'], ENT_COMPAT, 'UTF-8') . '">';
    	echo '  <member_id>' . htmlspecialchars($result['serverMemberId'], ENT_COMPAT, 'UTF-8') . '</member_id>';
    	echo '  <session_id>' . htmlspecialchars($result['serverSessionId'], ENT_COMPAT, 'UTF-8') . '</session_id>';
		echo '  <pass_hash>' . htmlspecialchars($result['serverPassHash'], ENT_COMPAT, 'UTF-8') . '</pass_hash>';
    	echo '</user>';
    }
    
    
    /**
     * An error occured.
     * I would normaly solve this by throwing an Exception and use an exception handler.
     *
     * @param string $message
     */
    protected function fail($message)
    {
        header("HTTP/1.1 406 Not Acceptable");
        echo $message;
        exit;
    }
    
    /**
     * Login failure.
     * I would normaly solve this by throwing a LoginException and use an exception handler.
     *
     * @param string $message
     */
    protected function failLogin($message)
    {
        header("HTTP/1.1 401 Unauthorized");
        echo $message;
        exit;
    }
}

// Execute controller command
if (realpath($_SERVER["SCRIPT_FILENAME"]) == realpath(__FILE__) && isset($_GET['cmd'])) {
    $ctl = new SingleSignOn_Server();
    $ctl->$_GET['cmd']();
}
